/**
* Copyright 2015 Palantir Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.palantir.giraffe.ssh.internal;
import java.io.IOException;
import java.net.URI;
import java.nio.file.CopyOption;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import com.palantir.giraffe.file.base.BaseFileSystemProvider;
import com.palantir.giraffe.file.base.CopyFlags;
import com.palantir.giraffe.file.base.CrossSystemTransfers;
import com.palantir.giraffe.file.base.LinkOptions;
import com.palantir.giraffe.file.base.attribute.ChmodFilePermissions;
import com.palantir.giraffe.file.base.attribute.PermissionChange;
import com.palantir.giraffe.file.base.attribute.PosixFileAttributeViews;
import com.palantir.giraffe.file.base.feature.LargeFileCopy;
import com.palantir.giraffe.file.base.feature.RecursiveCopy;
import com.palantir.giraffe.file.base.feature.RecursiveDelete;
import com.palantir.giraffe.file.base.feature.RecursivePermissions;
import net.schmizz.sshj.xfer.scp.SCPFileTransfer;
/**
* Provides access to a remote file systems using SSH and SFTP.
*
* @author bkeyes
*/
public final class SshFileSystemProvider extends BaseFileSystemProvider<SshPath>
implements RecursiveDelete, RecursivePermissions, LargeFileCopy, RecursiveCopy {
public SshFileSystemProvider() {
super(SshPath.class);
}
@Override
public String getScheme() {
return SshUris.getFileScheme();
}
@Override
public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
SshUris.checkFileUri(uri);
InternalSshSystemRequest request = new InternalSshSystemRequest(uri, env);
if (request.isInsternalSource()) {
// this is being requested as part of HostControlSystem creation
return new SshFileSystem(this, request);
} else {
return SshHostControlSystem.builder(request)
.setFileSystem(this)
.setExecutionSystem()
.build()
.getFileSystem();
}
}
@Override
public FileSystem getFileSystem(URI uri) {
SshUris.checkFileUri(uri);
throw new FileSystemNotFoundException(uri.toString());
}
@Override
public void createSymbolicLink(Path link, Path target, FileAttribute<?>... attrs)
throws IOException {
SshPath linkToCreate = checkPath(link);
SshPath targetFile = checkPath(target);
if (!isSameUri(linkToCreate, targetFile)) {
// TODO(bkeyes): fix this exception (IOException)
throw new InvalidPathException(link.toString(),
"has a different URI than target " + target.toString());
}
logger(linkToCreate).debug("symlinking {} to {}", link, target);
SshSymlinks.create(linkToCreate, targetFile, attrs);
}
@Override
public Path readSymbolicLink(Path link) throws IOException {
SshPath symlink = checkPath(link);
return link.getFileSystem().getPath(SshSymlinks.read(symlink));
}
@Override
public void deleteRecursive(Path path) throws IOException {
if (!deleteRecursiveIfExists(path)) {
throw new NoSuchFileException(path.toString());
}
}
@Override
public boolean deleteRecursiveIfExists(Path path) throws IOException {
SshPath file = checkPath(path);
if (Files.exists(path, LinkOption.NOFOLLOW_LINKS)) {
logger(file).debug("recursively deleting {}", file);
SshSameHostFileHelper.deleteRecursive(file);
return true;
} else {
return false;
}
}
@Override
public void changePermissionsRecursive(Path path, PermissionChange change,
Set<PosixFilePermission> permissions) throws IOException {
SshPath file = checkPath(path);
String mode = ChmodFilePermissions.toMode(change, permissions);
logger(file).debug("recursively changing permissions of {} to {}", file, mode);
SshSameHostFileHelper.changePermissionsRecursive(file, mode);
}
@Override
public void copy(Path source, Path target, CopyOption... options) throws IOException {
SshPath sourceFile = checkPath(source);
SshPath targetFile = checkPath(target);
CopyFlags flags = CopyFlags.fromOptions(options);
if (!Files.isSameFile(sourceFile, targetFile)) {
if (Files.isDirectory(sourceFile, LinkOptions.toArray(flags.followLinks))) {
logger(sourceFile).debug("copying directory {} to {}", source, target.toUri());
copyDirectory(sourceFile, targetFile, flags);
} else if (isSameUri(sourceFile, targetFile)) {
logger(sourceFile).debug("copying file {} to {}", source, target);
SshSameHostFileHelper.copyFile(sourceFile, targetFile, flags);
} else {
logger(sourceFile).debug("copying file {} to {}", source, target.toUri());
CrossSystemTransfers.copyFile(sourceFile, targetFile, flags);
}
}
}
@Override
public void copyLarge(Path source, Path target) throws IOException {
copyRecursive(source, target);
}
@Override
public void copyRecursive(Path source, Path target) throws IOException {
boolean isSourceSsh = isCompatible(source);
boolean isTargetSsh = isCompatible(target);
String absSource = source.toAbsolutePath().toString();
String absTarget = target.toAbsolutePath().toString();
if (isLocal(source) || isLocal(target)) {
if (isTargetSsh) {
SshPath sshTarget = checkPath(target);
logger(sshTarget).debug("scp from {} to {}", source.toUri(), absTarget);
SCPFileTransfer scp = sshTarget.getFileSystem().getScpFileTransfer();
scp.upload(absSource, absTarget);
} else {
SshPath sshSource = checkPath(source);
logger(sshSource).debug("scp from {} to {}", absSource, target.toUri());
SCPFileTransfer scp = sshSource.getFileSystem().getScpFileTransfer();
scp.download(absSource, absTarget);
}
} else if (isSourceSsh && isTargetSsh && isSameUri(checkPath(source), checkPath(target))) {
SshPath sshSource = checkPath(source);
SshPath sshTarget = checkPath(target);
logger(sshSource).debug("recursively copying path {} to {}", absSource, absTarget);
SshSameHostFileHelper.copyRecursive(sshSource, sshTarget);
} else {
throw new UnsupportedOperationException();
}
}
private void copyDirectory(SshPath source, SshPath target, CopyFlags flags) throws IOException {
createDirectory(target);
if (flags.copyAttributes) {
LinkOption[] options = LinkOptions.toArray(flags.followLinks);
PosixFileAttributeViews.copyAttributes(source, target, options);
}
}
@Override
public void move(Path source, Path target, CopyOption... options) throws IOException {
SshPath sourceFile = checkPath(source);
SshPath targetFile = checkPath(target);
CopyFlags flags = CopyFlags.fromOptions(options);
if (!Files.isSameFile(sourceFile, targetFile)) {
if (isSameUri(sourceFile, targetFile)) {
logger(sourceFile).debug("moving path {} to {}", source, target);
SshSameHostFileHelper.movePath(sourceFile, targetFile, flags);
} else {
logger(sourceFile).debug("moving path {} to {}", source, target.toUri());
moveCrossHost(sourceFile, targetFile, flags);
}
}
}
private void moveCrossHost(SshPath source, SshPath target, CopyFlags flags) throws IOException {
BasicFileAttributes attrs = readAttributes(source, BasicFileAttributes.class);
if (attrs.isDirectory()) {
CrossSystemTransfers.moveDirectory(source, target, flags);
} else {
CrossSystemTransfers.moveFile(source, target, flags);
}
}
private boolean isSameUri(SshPath path, SshPath other) {
return path.getFileSystem().uri().equals(other.getFileSystem().uri());
}
@Override
public Path getPath(URI uri) {
throw new UnsupportedOperationException();
}
@Override
public FileStore getFileStore(Path path) throws IOException {
throw new UnsupportedOperationException();
}
private static Logger logger(SshPath path) {
return path.getFileSystem().logger();
}
private static boolean isLocal(Path path) {
return path.getFileSystem().equals(FileSystems.getDefault());
}
}